package io.kiki.stack.netty.channelManager;

import io.kiki.stack.netty.config.ClientOption;
import io.kiki.stack.netty.config.Config;
import io.kiki.stack.netty.util.InetSocketAddressUtil;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
import io.netty.util.Attribute;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.net.SocketAddress;

@Slf4j
@Setter
@Getter
@Sharable
public class ChannelEventHandler extends ChannelDuplexHandler {

    private ChannelManager channelManager;

    private ChannelEventListener channelEventListener;

    private ChannelEventExecutor channelEventExecutor;

    private ReconnectManager reconnectManager;

    private Config config;

    public ChannelEventHandler() {

    }

    public ChannelEventHandler(Config config) {
        this.config = config;
    }

    @Override
    public void connect(ChannelHandlerContext channelHandlerContext, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise channelPromise) throws Exception {
        if (log.isInfoEnabled()) {
            final String local = localAddress == null ? null : InetSocketAddressUtil.parseSocketAddressToString(localAddress);
            final String remote = remoteAddress == null ? "UNKNOWN" : InetSocketAddressUtil.parseSocketAddressToString(remoteAddress);
            if (local == null) {
                if (log.isInfoEnabled()) {
                    log.info("Try connect to {}", remote);
                }
            } else {
                if (log.isInfoEnabled()) {
                    log.info("Try connect from {} to {}", local, remote);
                }
            }
        }
        super.connect(channelHandlerContext, remoteAddress, localAddress, channelPromise);
    }

    @Override
    public void disconnect(ChannelHandlerContext channelHandlerContext, ChannelPromise channelPromise) throws Exception {
        super.disconnect(channelHandlerContext, channelPromise);
    }

    @Override
    public void close(ChannelHandlerContext channelHandlerContext, ChannelPromise channelPromise) throws Exception {
        Attribute attribute = channelHandlerContext.channel().attr(Channel.CHANNEL);
        if (null != attribute) {
            final Channel channel = (Channel) attribute.get();
            if (channel != null) {
                channel.onClose();
            }
        }
        super.close(channelHandlerContext, channelPromise);
    }

    @Override
    public void channelRegistered(ChannelHandlerContext channelHandlerContext) throws Exception {
        super.channelRegistered(channelHandlerContext);
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext channelHandlerContext) throws Exception {
        super.channelUnregistered(channelHandlerContext);
    }

    @Override
    public void channelActive(ChannelHandlerContext channelHandlerContext) throws Exception {
        super.channelActive(channelHandlerContext);
    }

    @Override
    public void channelInactive(ChannelHandlerContext channelHandlerContext) throws Exception {
        super.channelInactive(channelHandlerContext);
        Attribute attribute = channelHandlerContext.channel().attr(Channel.CHANNEL);
        if (null != attribute) {
            Channel channel = (Channel) attribute.get();
            // if conn is null, means that channel has been inactive before binding with Channel
            // this situation will fire a CLOSE event in ChannelFactory
            if (channel != null) {
                userEventTriggered(channelHandlerContext, ChannelEventType.CLOSE);
            }
        }
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext channelHandlerContext, Object event) throws Exception {
        if (event instanceof ChannelEventType) {
            ChannelEventType channelEventType = (ChannelEventType) event;
            io.netty.channel.Channel channel = channelHandlerContext.channel();
            if (channel == null) {
                log.warn("channel null when handle user triggered event in ChannelEventHandler! eventType: {}", channelEventType.name());
                return;
            }

            Channel channel1 = channel.attr(Channel.CHANNEL).get();
            if (channel1 == null) {
                log.error("[BUG]channel is null when handle user triggered event in channelEventHandler! eventType: {}", channelEventType.name());
                return;
            }
            final String localAddress = InetSocketAddressUtil.parseLocalAddress(channelHandlerContext.channel());
            final String remoteAddress = InetSocketAddressUtil.parseRemoteAddress(channelHandlerContext.channel());
            log.info("trigger user event, local[{}], remote[{}], event: {}", localAddress, remoteAddress, channelEventType.name());

            switch (channelEventType) {
            case CONNECT:
                onEvent(channel1, channel1.getUrl().getOriginUrl(), ChannelEventType.CONNECT);
                break;
            case CONNECT_FAILED:
            case CLOSE:
            case EXCEPTION:
                submitReconnectTaskIfNecessary(channel1.getUrl());
                onEvent(channel1, channel1.getUrl().getOriginUrl(), channelEventType);
                break;
            default:
                log.error("[BUG]unknown event: {}", channelEventType.name());
                break;
            }
        } else {
            super.userEventTriggered(channelHandlerContext, event);
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext channelHandlerContext, Throwable cause) throws Exception {
        final String localAddress = InetSocketAddressUtil.parseLocalAddress(channelHandlerContext.channel());
        final String remoteAddress = InetSocketAddressUtil.parseRemoteAddress(channelHandlerContext.channel());
        log.warn("ExceptionCaught in channel: local[{}], remote[{}], close the channel! Cause[{}:{}]", localAddress, remoteAddress, cause.getClass().getSimpleName(), cause.getMessage());
        channelHandlerContext.channel().close();
    }

    private void submitReconnectTaskIfNecessary(Url url) {
        if (config.option(ClientOption.Channel_reconnect_switch) && reconnectManager != null) {
            reconnectManager.reconnect(url);
        }
    }

    private void onEvent(final Channel channel, final String remoteAddress, final ChannelEventType channelEventType) {
        if (this.channelEventListener != null) {
            this.channelEventExecutor.onEvent(new Runnable() {
                @Override
                public void run() {
                    ChannelEventHandler.this.channelEventListener.onEvent(channelEventType, channel, remoteAddress);
                }
            });
        }
    }

    public void setChannelEventListener(ChannelEventListener channelEventListener) {
        if (channelEventListener != null) {
            this.channelEventListener = channelEventListener;
            if (this.channelEventExecutor == null) {
                this.channelEventExecutor = new ChannelEventExecutor();
            }
        }
    }


}
